using System;
using System.Collections;
using System.Data;
using Neo.Core;
using Neo.Core.Qualifiers;
using Neo.Core.Util;

namespace Neo.Framework
{
    /// <summary>
    /// The <see cref="ManyToManyRelation{T}"/> class wraps an ordinary <see cref="ObjectRelationBase"/> to provide
    /// a type-safe collection compile-time.
    /// </summary>
    /// <typeparam name="T">The type of elements in the list.</typeparam>
    /// <remarks>
    /// The <see cref="ManyToManyRelation{T}"/> class is a descendant of the <see cref="ObjectRelationBase"/> class
    /// and is the base class for the set of objects which are part of a relation.
    /// </remarks>
    /// <author>Paul Gielens</author>
    [Serializable]
    public class ManyToManyRelation<T> : IEnumerable where T : EntityObject //where V : EntityObject
    {
        private readonly IObjectContext _context;
        private readonly EntityObject _entity;
        private readonly Type _manyToManyEntity;
        private readonly IEntityMap _manyToManyMap;
        private readonly RelationInfo _otherRelation;
        private readonly string[] _primaryKeyColumns;
        private readonly RelationInfo _thisRelation;

        /// <summary>
        /// Initializes a new instance of the <see cref="ManyToManyRelation&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="eo">The eo.</param>
        /// <param name="manyToManyEntity">The many to many entity.</param>
        /// <param name="thisRelationName">Name of the this relation.</param>
        /// <param name="otherRelationName">Name of the other relation.</param>
        public ManyToManyRelation(EntityObject eo, Type manyToManyEntity, string thisRelationName, string otherRelationName)
        {
            _entity = eo;
            _context = eo.Context;

            _manyToManyEntity = manyToManyEntity;
            _manyToManyMap = _context.EntityMapFactory.GetMap(manyToManyEntity);

            _thisRelation = _manyToManyMap.GetRelationInfo(thisRelationName);
            _otherRelation = _manyToManyMap.GetRelationInfo(otherRelationName);

            _primaryKeyColumns = _manyToManyMap.PrimaryKeyColumns;
        }

        /// <summary>
        /// Gets the item at the specified index.
        /// </summary>
        /// <value></value>
        public T this[int index]
        {
            get { return (T) otherList[index]; }
        }

        private ObjectList<T> otherList
        {
            get
            {
                string path = null;
                string propName = null;
                object val = null;

                IEntityMap map = _context.EntityMapFactory.GetMap(_otherRelation.ParentEntity.ConcreteObjectType);

                FetchSpecification spec = new FetchSpecification(map);

                foreach (string s in _manyToManyMap.Relations)
                {
                    RelationInfo r = _manyToManyMap.GetRelationInfo(s);
                    RelationInfo o = GetInverseRelationship(r);

                    if (o != null && o.Equals(_otherRelation))
                    {
                        path = map.GetRelationName(_otherRelation);
                        propName = _thisRelation.ChildKey;
                        try
                        {
                            val = _entity.GetProperty(_thisRelation.ParentKey);
                        }
                        catch (RowNotInTableException)
                        {
                            val = null;
                        }
                        continue;
                    }
                }

                spec.Qualifier = new PathQualifier(path, new PropertyQualifier(propName, new EqualsPredicate(val)));

                return new ObjectList<T>(_context.GetObjects(spec));
            }
        }

        private ObjectList<T> manyToManyList
        {
            get
            {
                string propName = null;
                object val = null;

                IEntityMap map = _manyToManyMap;

                FetchSpecification spec = new FetchSpecification(map);

                foreach (string s in _manyToManyMap.Relations)
                {
                    RelationInfo r = _manyToManyMap.GetRelationInfo(s);
                    RelationInfo o = GetInverseRelationship(r);

                    if (o != null && o.Equals(_thisRelation))
                    {
                        propName = _thisRelation.ChildKey;
                        try
                        {
                            val = _entity.GetProperty(_thisRelation.ParentKey);
                        }
                        catch (RowNotInTableException)
                        {
                            val = null;
                        }
                        continue;
                    }
                }

                spec.Qualifier = new PropertyQualifier(propName, new EqualsPredicate(val));

                return new ObjectList<T>(_context.GetObjects(spec));
            }
        }

        /// <summary>
        /// Gets the count.
        /// </summary>
        /// <value>The count.</value>
        public int Count
        {
            get 
            { 
            	try
            	{
            		return otherList.Count;
            	}
            	catch
            	{
            		return 0;
            	}
            }
        }

        #region IEnumerable Members

        ///<summary>
        ///Returns an enumerator that iterates through a collection.
        ///</summary>
        ///
        ///<returns>
        ///An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
        ///</returns>
        ///<filterpriority>2</filterpriority>
        public IEnumerator GetEnumerator()
        {
            return otherList.GetEnumerator();
        }

        #endregion

        /// <summary>
        /// Adds the specified new entity.
        /// </summary>
        /// <param name="newEntity">The new entity.</param>
        /// <returns></returns>
        public void Add(T newEntity)
        {
            object[] parms = new object[_primaryKeyColumns.Length];

            for (int i = 0; i < _primaryKeyColumns.Length; i++)
            {
                parms[i] = getValueForKey(_primaryKeyColumns[i], newEntity);
            }
            try
            {
                _context.CreateObject(_manyToManyEntity, parms);
            }
            catch (InvalidOperationException)
            {
                object[] rev = new object[] {parms[1], parms[0]};
                _context.CreateObject(_manyToManyEntity, rev);
            }
        }

        private object getValueForKey(string primaryKey, T newEntity)
        {
            foreach (string s in _manyToManyMap.Relations)
            {
                RelationInfo r = _manyToManyMap.GetRelationInfo(s);
                RelationInfo o = GetInverseRelationship(r);
                if (o.ChildKey.Equals(primaryKey))
                {
                    if (o.Equals(_thisRelation))
                        return _entity.GetProperty(o.ParentKey);
                    if (o.Equals(_otherRelation))
                        return newEntity.GetProperty(o.ParentKey);
                }
            }
            return null;
        }

        /// <summary>
        /// Adds all the items in the list to this collection.
        /// </summary>
        /// <param name="list">The list.</param>
        public void AddAll(IEnumerable list)
        {
            foreach (T t in list)
            {
                Add(t);
            }
        }

        /// <summary>
        /// Removes the specified existing entity.
        /// </summary>
        /// <param name="existingEntity">The existing entity.</param>
        public void Remove(T existingEntity)
        {
            IList list = GetList(existingEntity);

            while (list.Count > 0)
            {
                EntityObject item = (EntityObject) list[0];
                item.Delete();
                list.RemoveAt(0);
            }
        }

        /// <summary>
        /// Clears this instance.
        /// </summary>
        public void Clear()
        {
            IList list = manyToManyList;
            while (list.Count > 0)
            {
                EntityObject obj = (EntityObject) list[0];
                obj.Delete();
                list.RemoveAt(0);
            }
        }

        private IList GetList(T existingEntity)
        {
            FetchSpecification spec = new FetchSpecification(_manyToManyMap);

            AndQualifier parms = new AndQualifier();

            for (int i = 0; i < _manyToManyMap.PrimaryKeyColumns.Length; i++)
            {
                foreach (string rel in _manyToManyMap.Relations)
                {
                    RelationInfo ri = _manyToManyMap.GetRelationInfo(rel);
                    if (ri.ParentKey.Equals(_manyToManyMap.PrimaryKeyColumns[i]))
                    {
                        if (ri.ParentEntity.ConcreteObjectType.Equals(existingEntity.GetType()))
                        {
                            parms.AddToQualifiers(new PropertyQualifier(ri.ParentKey, new EqualsPredicate(existingEntity.GetProperty(ri.ParentKey))));
                            continue;
                        }
                        else if (ri.ParentEntity.ConcreteObjectType.Equals(_entity.GetType()))
                        {
                            parms.AddToQualifiers(new PropertyQualifier(ri.ParentKey, new EqualsPredicate(_entity.GetProperty(ri.ParentKey))));
                            continue;
                        }
                        else
                        {
                            throw new Exception("Fuck!");
                        }
                    }
                }
            }

            spec.Qualifier = parms;

            return _context.GetObjects(spec);
        }


        /// <summary>
        /// Determines whether [contains] [the specified existing entity].
        /// </summary>
        /// <param name="existingEntity">The existing entity.</param>
        /// <returns>
        /// 	<c>true</c> if [contains] [the specified existing entity]; otherwise, <c>false</c>.
        /// </returns>
        public bool Contains(T existingEntity)
        {
            return GetList(existingEntity).Count > 0;
        }

        /// <summary>
        /// Gets the read only list.
        /// </summary>
        /// <returns></returns>
        public ObjectList<T> GetReadOnlyList()
        {
            ObjectList<T> resultSet = new ObjectList<T>();
            CopyToListAndMakeReadOnly(resultSet);
            return resultSet;
        }

        /// <summary>
        /// Gets the sorted list.
        /// </summary>
        /// <param name="propName">Name of the prop.</param>
        /// <param name="dir">The dir.</param>
        /// <returns></returns>
        public ObjectList<T> GetSortedList(string propName, SortDirection dir)
        {
            ObjectList<T> resultSet = new ObjectList<T>();
            CopyToListAndSort(resultSet, propName, dir);
            return resultSet;
        }

        /// <summary>
        /// Finds the specified qualifier format.
        /// </summary>
        /// <param name="qualifierFormat">The qualifier format.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public ObjectList<T> Find(string qualifierFormat, params object[] parameters)
        {
            return otherList.Find(qualifierFormat, parameters);
        }

        /// <summary>
        /// Finds the unique.
        /// </summary>
        /// <param name="qualifierFormat">The qualifier format.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public T FindUnique(string qualifierFormat, params object[] parameters)
        {
            return (T) otherList.FindUnique(qualifierFormat, parameters);
        }

        /// <summary>
        /// Finds the first.
        /// </summary>
        /// <param name="qualifierFormat">The qualifier format.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns></returns>
        public T FindFirst(string qualifierFormat, params object[] parameters)
        {
            return (T) otherList.FindFirst(qualifierFormat, parameters);
        }

        private void CopyToListAndMakeReadOnly(ObjectListBase list)
        {
            foreach (object o in otherList)
                ((IList) list).Add(o);
            list.MakeReadOnly();
        }

        private void CopyToListAndSort(ObjectListBase list, string propName, SortDirection dir)
        {
            foreach (object o in otherList)
                ((IList) list).Add(o);
            list.Sort(propName, dir);
            list.MakeReadOnly();
        }

        private RelationInfo GetInverseRelationship(RelationInfo relation)
        {
            foreach (string s in relation.ParentEntity.Relations)
            {
                RelationInfo r = relation.ParentEntity.GetRelationInfo(s);
                if ((r.ParentEntity.TableName == relation.ParentEntity.TableName) && (r.ParentKey == relation.ParentKey) && (r.ChildKey == relation.ChildKey))
                    return r;
            }
            return null;
        }
    }
}
